﻿/*
 * Copyright (c) 2015, the DeltaSync# project. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
using System;
using System.IO;

namespace PM.Net.DeltaSync.hu01
{
    /// <summary>
    /// Description of BinaryAccess.
    /// </summary>
    public class BinaryAccess {
        private byte[] buffer;
        private int    start;
        private int    length;
        public BinaryAccess(byte[] buffer, int offset, int length) {
            if (buffer == null)
                throw new ArgumentNullException("Buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("Offset");
            if (length < 0)
                throw new ArgumentOutOfRangeException("Length");
            if (offset + length > buffer.Length)
                throw new ArgumentOutOfRangeException("Length");
            this.buffer = buffer;
            this.start  = offset;
            this.length = length;
        }
        
        public BinaryAccess GetAccess(int offset, int length) {
            if (offset < 0)
                throw new ArgumentOutOfRangeException("Offset");
            if (offset + length > this.length)
                throw new ArgumentOutOfRangeException("Length");
            return new BinaryAccess(this.buffer, this.start + offset, length);
        }

        public int Length {
            get { return this.length; }
        }
        
        public byte ReadByte(int offset) {
            if (offset < 0 || offset >= this.length)
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            return this.buffer[offset];
        }
        public short ReadShort(int offset) {
            if (offset < 0 || offset >= (this.length - 1))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            unchecked {
                return (short)((int)this.buffer[offset] | (int)this.buffer[offset + 1] << 8);
            }
        }
        public ushort ReadUShort(int offset) {
            if (offset < 0 || offset >= (this.length - 1))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            return (ushort)((uint)this.buffer[offset] | (uint)this.buffer[offset + 1] << 8);
        }
        public int ReadInt(int offset) {
            if (offset < 0 || offset >= (this.length - 3))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            unchecked {
                return (int)this.buffer[offset] | (int)this.buffer[offset + 1] << 8 | (int)this.buffer[offset + 2] << 16 | (int)this.buffer[offset + 3] << 24;
            }
         }
        public uint ReadUInt(int offset) {
            if (offset < 0 || offset >= (this.length - 3))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            return (uint)this.buffer[offset] | (uint)this.buffer[offset + 1] << 8 | (uint)this.buffer[offset + 2] << 16 | (uint)this.buffer[offset + 3] << 24;
        }
        
        public void Write(int offset, byte value) {
            if (offset < 0 || offset >= this.length)
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            this.buffer[offset] = value;
        }
        public void Write(int offset, short value) {
            if (offset < 0 || offset >= (this.length - 1))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            this.buffer[offset]     = (byte) value;
            this.buffer[offset + 1] = (byte)(value >>  8);
        }
        public void Write(int offset, int value) {
            if (offset < 0 || offset >= (this.length - 3))
                throw new ArgumentOutOfRangeException("Offset");
            offset += this.start;
            this.buffer[offset]     = (byte) value;
            this.buffer[offset + 1] = (byte)(value >>  8);
            this.buffer[offset + 2] = (byte)(value >> 16);
            this.buffer[offset + 3] = (byte)(value >> 24);
        }
        
        public byte[] ToArray() {
            byte[] data = new byte[this.Length];
            Array.Copy(this.buffer, this.start, data, 0, this.length);
            return data;
        }
        public void WriteTo(Stream output) {
            output.Write(this.buffer, this.start, this.length);
        }
        public void WriteTo(BinaryWriter writer) {
            writer.Append(this.buffer, this.start, this.length);
        }
    }
    
    /// <summary>
    /// Description of BinaryWriter.
    /// </summary>
    public class BinaryWriter {
        private MemoryStream stream;
        private int          position;
        
        public BinaryWriter(int capacity) {
            this.stream = new MemoryStream(capacity);
        }
        
        public int Capacity {
            get { return this.stream.Capacity;  }
            set { this.stream.Capacity = value; }
        }

        public void Clear() {
            this.position        = 0;
            this.stream.Position = 0;
            this.stream.SetLength(0);
        }    
        
        public byte[] GetBuffer() {
            return this.stream.GetBuffer();
        }
        public int Position {
            get { return this.position;  }
        }
        public int AdvancePosition(int length) {
            if (length < 0)
                throw new ArgumentOutOfRangeException("Length");
            this.position += length;
            return this.position;
        }
        public int Remaining {
            get { return this.Length - this.Position; }
        }
        public int Length {
            get { return (int)this.stream.Length; }
        }
        public void Extend(int size) {
            if (size < 0)
                throw new ArgumentOutOfRangeException("Size");
            this.stream.SetLength(this.stream.Length + size);
        }
        
        public void Compact() {
            if (this.Position > 0) {
                 if (this.Position == this.Length)
                    Clear();
                 else {
                     byte[] data = this.stream.GetBuffer();
                     long   len  = this.stream.Length - this.Position;
                     Array.Copy(data, this.Position, data, 0, len);
                     this.position        = 0;
                     this.stream.Position = len;
                     this.stream.SetLength(len);
                 }
            }
        }
        
        public void Append(byte[] buffer, int offset, int length) {
            this.stream.Write(buffer, offset, length);
        }
        public int Retrive(byte[] buffer, int offset, int length) {
            if (length < 0)
                throw new ArgumentOutOfRangeException("Length");
            int n = Math.Min(length, this.Remaining);
            if (n > 0) {
                Array.Copy(this.stream.GetBuffer(), this.position, buffer, offset, n);
                this.position += n;
            }
            return n;
        }
        
        public BinaryAccess GetAccess() {
            return new BinaryAccess(this.stream.GetBuffer(), this.Position, this.Length - this.Position);
        }
        public BinaryAccess GetAccess(int offset, int length) {
            if (offset < 0)
                throw new ArgumentOutOfRangeException("Offset");
            if (length > this.Length - this.Position)
                throw new ArgumentOutOfRangeException("Length");
            return new BinaryAccess(this.stream.GetBuffer(), this.Position + offset, length);
        }
    }
}
